package com.qingcloud.fexecutor.base.handler.dynamic;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.qingcloud.base.context.UserContextHolder;
import com.qingcloud.base.utils.LoginUtils;
import com.qingcloud.fexecutor.base.handler.ApiHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.ssssssss.magicapi.core.config.MagicConfiguration;
import org.ssssssss.magicapi.core.config.WebSocketSessionManager;
import org.ssssssss.magicapi.core.context.CookieContext;
import org.ssssssss.magicapi.core.context.RequestContext;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.magicapi.core.context.SessionContext;
import org.ssssssss.magicapi.core.exception.ValidateException;
import org.ssssssss.magicapi.core.interceptor.RequestInterceptor;
import org.ssssssss.magicapi.core.interceptor.ResultProvider;
import org.ssssssss.magicapi.core.model.Options;
import org.ssssssss.magicapi.modules.servlet.ResponseModule;
import org.ssssssss.magicapi.utils.ScriptManager;
import org.ssssssss.script.MagicScriptContext;
import org.ssssssss.script.MagicScriptDebugContext;
import org.ssssssss.script.exception.MagicScriptAssertException;
import org.ssssssss.script.exception.MagicScriptException;
import org.ssssssss.script.parsing.Span;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

import static org.springframework.http.HttpHeaders.*;
import static org.ssssssss.magicapi.core.config.Constants.*;
import static org.ssssssss.magicapi.core.config.MessageType.EXCEPTION;

/**
 * 动态API执行
 */
public class DynamicApiHandler implements ApiHandler {

    private static final Logger logger = LoggerFactory.getLogger(DynamicApiHandler.class);
    private static final Map<String, Object> EMPTY_MAP = new HashMap<>();
    private static final List<String> DEFAULT_ALLOW_READ_RESPONSE_HEADERS = Arrays.asList(
            ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_METHODS,
            CONTENT_TYPE, DATE, SERVER, SET_COOKIE, CONNECTION, CONTENT_LENGTH, CONTENT_ENCODING, TRANSFER_ENCODING, VARY);

    //运行配置
    private MagicConfiguration configuration;

    //结果处理
    private ResultProvider resultProvider;

    //数据源ID
    private String dsId;

    //动态脚本
    private String script;

    public DynamicApiHandler(MagicConfiguration configuration, String dsId, String script) {
        this.configuration = configuration;
        this.resultProvider = configuration.getResultProvider();
        this.dsId = dsId;
        this.script = script;
    }

    /**
     * 动态脚本API执行入口
     */
    @Override
    public Object invoke(HttpServletRequest request, HttpServletResponse response,
                         Map<String, String> headerMap, Map<String, Object> querys,
                         Map<String, Object> forms, Map<String, Object> paths,
                         Object bodyValue) throws Throwable {
        //API ID和客户端ID
        String apiId = IdUtil.simpleUUID();
        querys.putAll(forms);
        Map<String, Object> headers = new HashMap<>(headerMap);
        //请求体
        RequestEntity requestEntity = RequestEntity.create()
                .request(request)
                .response(response)
                .setHeaders(headers)
                .parameters(querys)
                .pathVariables(paths)
                .setRequestBody(bodyValue)
                .requestedFromTest(true);
        MagicScriptContext context = createMagicScriptContext(apiId);
        requestEntity.setMagicScriptContext(context);
        try {
            //请求参数包装到params对象中
            context.set("params", requestEntity.getParameters());
            //请求参数包装到params对象中
            context.set("paths", requestEntity.getPathVariables());
            //当前登录访问用户信息
            context.set("currentUser", LoginUtils.getCurrentUser(true));
            //默认数据源ID
            context.set(Options.DEFAULT_DATA_SOURCE.getValue(), this.dsId);
            //所有请求参数也放到根参数中
            context.putMapIntoContext(requestEntity.getParameters());
            // 设置 cookie 变量
            context.set(VAR_NAME_COOKIE, new CookieContext(requestEntity.getRequest()));
            // 设置 header 变量
            context.set(VAR_NAME_HEADER, headers);
            // 设置 session 变量
            context.set(VAR_NAME_SESSION, new SessionContext(requestEntity.getRequest().getSession()));
            // 设置 body 变量
            if (bodyValue != null) {
                context.set(VAR_NAME_REQUEST_BODY, bodyValue);
            }
        } catch (ValidateException e) {
            return afterCompletion(requestEntity, resultProvider.buildResult(requestEntity, RESPONSE_CODE_INVALID, e.getMessage()));
        } catch (Throwable root) {
            return processException(requestEntity, root);
        }
        RequestContext.setRequestEntity(requestEntity);
        Object value;
        // 执行前置拦截器
        if ((value = doPreHandle(requestEntity)) != null) {
            return afterCompletion(requestEntity, value);
        }
        String clientId = request.getHeader("Api-Request-Client-Id");
        try {
            if (StrUtil.isNotBlank(clientId) && context instanceof MagicScriptDebugContext) {
                WebSocketSessionManager.addMagicScriptContext(clientId, (MagicScriptDebugContext) context);
            }
            return invokeRequest(requestEntity);
        } finally {
            if (StrUtil.isNotBlank(clientId)) {
                WebSocketSessionManager.removeMagicScriptContext(clientId);
            }
        }
    }

    private Object invokeRequest(RequestEntity requestEntity) throws Throwable {
        try {
            MagicScriptContext context = requestEntity.getMagicScriptContext();
            Object result = ScriptManager.executeScript(this.script, context);
            Object value = result;
            // 执行后置拦截器
            if ((value = doPostHandle(requestEntity, value)) != null) {
                return afterCompletion(requestEntity, value);
            }
            // 对返回结果包装处理
            return afterCompletion(requestEntity, response(requestEntity, result));
        } catch (Throwable root) {
            return processException(requestEntity, root);
        } finally {
            RequestContext.remove();
        }
    }

    private Object processException(RequestEntity requestEntity, Throwable root) throws Throwable {
        MagicScriptException se = null;
        Throwable parent = root;
        do {
            if (parent instanceof MagicScriptAssertException) {
                MagicScriptAssertException sae = (MagicScriptAssertException) parent;
                return afterCompletion(requestEntity, resultProvider.buildResult(requestEntity, sae.getCode(), sae.getMessage()), root);
            }
            if (parent instanceof MagicScriptException) {
                se = (MagicScriptException) parent;
            }
        } while ((parent = parent.getCause()) != null);
        if (se != null && requestEntity.isRequestedFromTest()) {
            Span.Line line = se.getLine();
            WebSocketSessionManager.sendByClientId(requestEntity.getDebugRequest().getRequestedClientId(), EXCEPTION, Arrays.asList(
                    requestEntity.getDebugRequest().getRequestedScriptId(),
                    se.getSimpleMessage(),
                    line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(), line.getStartCol(), line.getEndCol())
            ));
        }
        if (configuration.isThrowException()) {
            afterCompletion(requestEntity, null, root);
            throw root;
        }
        logger.error("接口{}请求出错", requestEntity.getRequest().getRequestURI(), root);
        return afterCompletion(requestEntity, resultProvider.buildException(requestEntity, root), root);
    }

    /**
     * 构建 MagicScriptContext
     */
    private MagicScriptContext createMagicScriptContext(String scriptName) {
        MagicScriptContext context = new MagicScriptContext();
        context.setScriptName(scriptName);
        return context;
    }


    /**
     * 包装返回结果
     */
    private Object response(RequestEntity requestEntity, Object value) {
        if (value instanceof ResponseEntity) {
            return value;
        } else if (value instanceof ResponseModule.NullValue) {
            return null;
        }
        return resultProvider.buildResult(requestEntity, value);
    }

    /**
     * 执行后置拦截器
     */
    private Object doPostHandle(RequestEntity requestEntity, Object value) throws Exception {
        for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
            Object target = requestInterceptor.postHandle(requestEntity, value);
            if (target != null) {
                return afterCompletion(requestEntity, target);
            }
        }
        return null;
    }

    private Object afterCompletion(RequestEntity requestEntity, Object returnValue) {
        return afterCompletion(requestEntity, returnValue, null);
    }

    private Object afterCompletion(RequestEntity requestEntity, Object returnValue, Throwable throwable) {
        for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
            try {
                requestInterceptor.afterCompletion(requestEntity, returnValue, throwable);
            } catch (Exception e) {
                logger.warn("执行afterCompletion出现出错", e);
            }
        }
        Set<String> exposeHeaders = new HashSet<>(16);
        if (returnValue instanceof ResponseEntity) {
            exposeHeaders.addAll(((ResponseEntity<?>) returnValue).getHeaders().keySet());
        }
        if (requestEntity.isRequestedFromTest()) {
            HttpServletResponse response = requestEntity.getResponse();
            exposeHeaders.addAll(response.getHeaderNames());
            exposeHeaders.addAll(DEFAULT_ALLOW_READ_RESPONSE_HEADERS);
        }
        if (!exposeHeaders.isEmpty()) {
            requestEntity.getResponse().setHeader(ACCESS_CONTROL_EXPOSE_HEADERS, String.join(",", exposeHeaders));
        }
        return returnValue;
    }

    /**
     * 执行前置拦截器
     */
    private Object doPreHandle(RequestEntity requestEntity) throws Exception {
        for (RequestInterceptor requestInterceptor : configuration.getRequestInterceptors()) {
            Object value = requestInterceptor.preHandle(requestEntity);
            if (value != null) {
                return value;
            }
        }
        return null;
    }

}
